/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.stetho.inspector.elements;

import com.facebook.stetho.common.Util;

import java.util.IdentityHashMap;
import java.util.Map;

import javax.annotation.Nullable;

public final class DescriptorMap implements DescriptorRegistrar {
  private final Map<Class<?>, Descriptor> mMap = new IdentityHashMap<>();
  private boolean mIsInitializing;
  private Descriptor.Host mHost;

  public DescriptorMap beginInit() {
    Util.throwIf(mIsInitializing);
    mIsInitializing = true;
    return this;
  }

  @Override
  public DescriptorMap registerDescriptor(Class<?> elementClass, Descriptor descriptor) {
    Util.throwIfNull(elementClass);
    Util.throwIfNull(descriptor);
    Util.throwIf(descriptor.isInitialized());
    Util.throwIfNot(mIsInitializing);

    // Cannot register two descriptors for one class
    if (mMap.containsKey(elementClass)) {
      throw new UnsupportedOperationException();
    }

    // Cannot reuse one descriptor for two classes
    if (mMap.containsValue(descriptor)) {
      throw new UnsupportedOperationException();
    }

    mMap.put(elementClass, descriptor);
    return this;
  }

  public DescriptorMap setHost(Descriptor.Host host) {
    Util.throwIfNull(host);
    Util.throwIfNot(mIsInitializing);
    Util.throwIfNotNull(mHost);

    mHost = host;
    return this;
  }

  public DescriptorMap endInit() {
    Util.throwIfNot(mIsInitializing);
    Util.throwIfNull(mHost);

    mIsInitializing = false;

    for (final Class<?> elementClass : mMap.keySet()) {
      final Descriptor descriptor = mMap.get(elementClass);

      if (descriptor instanceof ChainedDescriptor) {
        final ChainedDescriptor chainedDescriptor = (ChainedDescriptor) descriptor;
        Class<?> superClass = elementClass.getSuperclass();
        Descriptor superDescriptor = getImpl(superClass);
        chainedDescriptor.setSuper(superDescriptor);
      }

      descriptor.initialize(mHost);
    }

    return this;
  }

  @Nullable
  public Descriptor get(Class<?> elementClass) {
    Util.throwIfNull(elementClass);
    Util.throwIf(mIsInitializing);
    return getImpl(elementClass);
  }

  @Nullable
  private Descriptor getImpl(final Class<?> elementClass) {
    Class<?> theClass = elementClass;
    while (theClass != null) {
      Descriptor descriptor = mMap.get(theClass);
      if (descriptor != null) {
        return descriptor;
      }
      theClass = theClass.getSuperclass();
    }
    return null;
  }
}
